﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Pap.Core.Utilities;
using Pap.Data;
using Pap.Data.Abstraction.Utils;
using Pap.Services;
using Pap.Services.FinancialServices;
using Pap.Services.WorkflowEngine;
using Pap.Web.Utils;

namespace Pap.Web.Modules.Loan.ViewModel
{
    /// <summary>
    /// 
    /// </summary>
    public class CreateBoardViewModel : IViewModel
    {
        #region [ fields ]
        /// <summary>
        /// 
        /// </summary>
        private readonly ILoanRepository _loanRepository;
        /// <summary>
        /// 
        /// </summary>
        private readonly IMembershipRepository _membershipRepository;
        /// <summary>
        /// 
        /// </summary>
        private readonly IAdministrationRepository _administrationRepository;
        /// <summary>
        /// 
        /// </summary>
        private readonly IWorkflowService _wfService;
        /// <summary>
        /// 
        /// </summary>
        private readonly IFinanceService _financeService;
        /// <summary>
        /// 
        /// </summary>
        private readonly ILoanExecutionService _loanExecutionService;
        #endregion

        #region [ Constructor ]
        /// <summary>
        /// Initializes a new instance of the <see cref="CreateBoardViewModel"/> class.
        /// </summary>
        /// <param name="loanRepo">The loan repo.</param>
        /// <param name="memberRepo">The member repo.</param>
        /// <param name="wfService">The wf service.</param>
        /// <param name="administrationRepository">The administration repository.</param>
        /// <param name="financeService">The finance service.</param>
        /// <param name="loanExecutionService">The loan execution service.</param>
        public CreateBoardViewModel(ILoanRepository loanRepo,
            IMembershipRepository memberRepo,
            IWorkflowService wfService,
            IAdministrationRepository administrationRepository,
            IFinanceService financeService,
            ILoanExecutionService loanExecutionService)
        {
            _loanRepository = loanRepo;
            _membershipRepository = memberRepo;
            _wfService = wfService;
            _administrationRepository = administrationRepository;
            _financeService = financeService;
            _loanExecutionService = loanExecutionService;
        }
        #endregion

        /// <summary>
        /// Gets all projects.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<IProject> GetAllProjects()
        {
            return _administrationRepository.FindProjects(pro => true);
        }

        /// <summary>
        /// Gets the current user project.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <returns></returns>
        public IEnumerable<IProject> GetCurrentUserProjects(Guid userId)
        {
            return _administrationRepository.GetUserProjects(userId);
        }

        /// <summary>
        /// Searches the users.
        /// </summary>
        /// <param name="partialName">The partial name.</param>
        /// <returns></returns>
        public IUser[] SearchUsers(string partialName)
        {
            partialName = partialName.ToLower();
            var results = _membershipRepository.FindUsers(u => u.DisplayName.ToLower().Contains(partialName) || u.Username.ToLower().Contains(partialName));
            return results.ToArray();
        }

        /// <summary>
        /// Gets all members.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<IBoardMember> GetAllMembers()
        {
            return _membershipRepository.FindMembers(u => true);
        }

        /// <summary>
        /// Creates the board.
        /// </summary>
        /// <param name="committeeId">The committee id.</param>
        /// <param name="boardDate">The board date.</param>
        /// <param name="boardMembers">The board members.</param>
        /// <param name="loansToDiscuss">The loans to discuss.</param>
        /// <param name="boardId">The board id.</param>
        public void CreateBoard(Guid committeeId, DateTime boardDate,
            IBoardMember[] boardMembers, Dictionary<Guid, string> loansToDiscuss,
            string boardId)
        {
            using (var unitOfWork = this.GetInstance<IUnitOfWork>())
            {
                IBoard board;
                //edit board
                if (!boardId.IsNullOrEmpty())
                {
                    board = _loanRepository.GetBoardByID(boardId.ToGuid());
                    _loanRepository.DeleteBoardAttendees(bA => bA.BoardID == board.BoardID);
                    _loanRepository.DeleteBoardLoans(bL => bL.BoardID == board.BoardID);
                }
                // insert board
                else
                {
                    board = this.GetInstance<IBoard>();
                    board.BoardID = Guid.NewGuid();
                    board.BoardNumber = _loanRepository.GetLastBoardNumber() + 1;
                    _loanRepository.CreateBoard(board);
                }

                board.BoardDate = boardDate;
                board.CommitteeID = committeeId;

                foreach (var member in boardMembers)
                {
                    var attendee = this.GetInstance<IBoardAttendee>();
                    attendee.BoardAttendeesID = Guid.NewGuid();
                    attendee.BoardID = board.BoardID;
                    attendee.UserID = member.BoardMemberID;
                    _loanRepository.CreateBoardAttendee(attendee);
                }
                _loanRepository.DeleteBoardLoans(bL => loansToDiscuss.Select(loan => loan.Key).Contains(bL.LoanID));
                foreach (var loan in loansToDiscuss)
                {
                    var boardLoan = this.GetInstance<IBoardLoan>();
                    boardLoan.LoanID = loan.Key;
                    boardLoan.EnteringNotes = loan.Value;
                    boardLoan.BoardID = board.BoardID;
                    _loanRepository.CreateBoardLoan(boardLoan);
                }

                var waitingLoanList = _wfService.GetInStep(Steps.BoardWaitingList);
                var loansToSubmit = loansToDiscuss.Where(loanId => waitingLoanList.Contains(loanId.Key));

                foreach (var item in loansToSubmit)
                {
                    Guid processInstanceId = _wfService.GetProcessInstanceIDByEntity(item.Key);
                    var step = _wfService.GetStep(processInstanceId);

                    if (!_loanExecutionService.ValidateGurantorWarning(
                    _loanRepository.GetCustomerLoanByID(item.Key).CustomerObj.SSN))
                    {
                        _wfService.Move(ActionTypes.FreezWithoutResearchAndStudy, processInstanceId, RegistryInfo.UserID,
                                        string.Empty,
                                        step.CompletionDate);
                    }
                    else
                    {
                        if (step.ActionType != ActionTypes.Unfreeze)
                            _wfService.Move(step.ActionType, processInstanceId, RegistryInfo.UserID, item.Value,
                                        step.CompletionDate);
                        else
                            _wfService.Move(ActionTypes.Submit, processInstanceId, RegistryInfo.UserID, item.Value,
                                        step.CompletionDate);
                    }
                }

                if (_wfService.GetProcessInstanceByEntity(board.BoardID) == null)
                    _wfService.CreateProcessInstance(Process.BoardCycle, board.BoardID, RegistryInfo.UserID, "");

                unitOfWork.Commit();
            }
        }

        /// <summary>
        /// Saves the board.
        /// </summary>
        /// <param name="committeeId">The committee id.</param>
        /// <param name="boardDate">The board date.</param>
        /// <param name="boardMembers">The board members.</param>
        /// <param name="loansData">The loans data.</param>
        /// <param name="boardId">The board id.</param>
        public void SaveBoard(Guid committeeId, DateTime boardDate, IBoardMember[] boardMembers, Dictionary<Guid, string> loansData, string boardId)
        {
            using (var unitOfWork = this.GetInstance<IUnitOfWork>())
            {
                IBoard board;
                if (!boardId.IsNullOrEmpty())
                {
                    board = _loanRepository.GetBoardByID(boardId.ToGuid());
                    _loanRepository.DeleteBoardAttendees(bA => bA.BoardID == board.BoardID);
                    _loanRepository.DeleteBoardLoans(bL => bL.BoardID == board.BoardID);
                }
                else
                {
                    board = this.GetInstance<IBoard>();
                    board.BoardID = Guid.NewGuid();
                    board.BoardNumber = _loanRepository.GetLastBoardNumber() + 1;
                    _loanRepository.CreateBoard(board);
                }

                board.BoardDate = boardDate;
                board.CommitteeID = committeeId;



                foreach (var member in boardMembers)
                {
                    var attendee = this.GetInstance<IBoardAttendee>();
                    attendee.BoardAttendeesID = Guid.NewGuid();
                    attendee.BoardID = board.BoardID;
                    attendee.UserID = member.BoardMemberID;
                    _loanRepository.CreateBoardAttendee(attendee);
                }
                foreach (var loan in loansData)
                {
                    var boardLoan = this.GetInstance<IBoardLoan>();
                    boardLoan.LoanID = loan.Key;
                    boardLoan.EnteringNotes = loan.Value;
                    boardLoan.BoardID = board.BoardID;
                    _loanRepository.CreateBoardLoan(boardLoan);
                }
                unitOfWork.Commit();
            }


        }

        /// <summary>
        /// Gets the un assigned loans.
        /// </summary>
        /// <param name="projectId">The project id.</param>
        /// <returns></returns>
        public IEnumerable GetUnAssignedLoans(Guid projectId)
        {
            var notSubmittedBoards = _loanRepository.FindBoard(b => true).Where(board => _wfService.GetProcessInstanceByEntity(board.BoardID) == null);

            var loansIDsAttachedToBoards = notSubmittedBoards.SelectMany(board => board.BoardLoans).Select(loan => loan.LoanID);

            var allLoans = _loanRepository.FindCustomerLoans(loan => (_wfService.GetInStep(Steps.BoardWaitingList).Contains(loan.LoanID))).Where(
                loan => loan.ProjectStage.ProjectID.Equals(projectId) && !loansIDsAttachedToBoards.Contains(loan.LoanID));

            var unAssignedLoans = from loan in allLoans
                                  orderby loan.LoanNumber ascending
                                  select new
                                  {
                                      loan.CustomerObj.CustomerName,
                                      loan.LoanCreatedDate,
                                      loan.LoanID,
                                      loan.LoanNumber,
                                      LoanType = loan.LoanTypeObj.Name,
                                      Notes = string.Empty,
                                      _wfService.GetStep(_wfService.GetProcessInstanceIDByEntity(loan.LoanID)).PreviousNotes,
                                      RequestedAmount = loan.LoanAmount,
                                      RequestedPaymentPeriod = loan.LoanPaymentPeriod,
                                      ResponsibleProposedAmount = loan.FeasibilityStudyObj != null ? loan.FeasibilityStudyObj.LoanAmount : 0.0,
                                      Submitted = false,
                                      Selected = !_loanExecutionService.ValidateGurantorWarning(loan.CustomerObj.SSN)
                                  };
            return unAssignedLoans;
        }

        /// <summary>
        /// Gets the un assigned loans by board id.
        /// </summary>
        /// <param name="boardId">The board id.</param>
        /// <returns></returns>
        public IEnumerable GetUnAssignedLoansByBoardId(Guid boardId)
        {
            var notSubmittedBoards = _loanRepository.FindBoard(b => true).Where(b => _wfService.GetProcessInstanceByEntity(b.BoardID) == null);
            var loansIDsAttachedToBoards = notSubmittedBoards.SelectMany(b => b.BoardLoans).Select(loan => loan.LoanID);
            var allLoans = _loanRepository.FindCustomerLoans(customerLoan => _wfService.GetInStep(Steps.BoardWaitingList).Contains(customerLoan.LoanID));
            var submittedLoansIDs = _wfService.GetInStep(Steps.BoardList);
            var board = _loanRepository.GetBoardByID(boardId);

            var boardLoans = from boardloan in board.BoardLoans
                             where !submittedLoansIDs.Contains(boardloan.LoanID)
                             orderby boardloan.LoanObj.LoanNumber ascending
                             select new
                             {
                                 boardloan.LoanObj.CustomerObj.CustomerName,
                                 boardloan.LoanObj.LoanCreatedDate,
                                 boardloan.LoanID,
                                 boardloan.LoanObj.LoanNumber,
                                 LoanType = boardloan.LoanObj.LoanTypeObj.Name,
                                 Notes = boardloan.EnteringNotes,
                                 _wfService.GetStep(_wfService.GetProcessInstanceIDByEntity(boardloan.LoanID)).PreviousNotes,
                                 RequestedAmount = boardloan.LoanObj.LoanAmount,
                                 RequestedPaymentPeriod = boardloan.LoanObj.LoanPaymentPeriod,
                                 ResponsibleProposedAmount = boardloan.LoanObj.FeasibilityStudyObj != null ? boardloan.LoanObj.FeasibilityStudyObj.LoanAmount : 0.0,
                                 Submitted = submittedLoansIDs.Contains(boardloan.LoanID)
                             };


            var unAssignedLoans = from loan in allLoans
                                  where !loansIDsAttachedToBoards.Contains(loan.LoanID)
                                  && loan.ProjectStage.ProjectID.Equals(board.BoardLoans.First().LoanObj.ProjectStage.ProjectID)
                                  orderby loan.LoanNumber ascending
                                  select new
                                  {
                                      loan.CustomerObj.CustomerName,
                                      loan.LoanCreatedDate,
                                      loan.LoanID,
                                      loan.LoanNumber,
                                      LoanType = _loanRepository.GetLoanTypeByID(loan.LoanTypeID).Name,
                                      Notes = string.Empty,
                                      _wfService.GetStep(_wfService.GetProcessInstanceIDByEntity(loan.LoanID)).PreviousNotes,
                                      RequestedAmount = loan.LoanAmount,
                                      RequestedPaymentPeriod = loan.LoanPaymentPeriod,
                                      ResponsibleProposedAmount = loan.FeasibilityStudyObj != null ? loan.FeasibilityStudyObj.LoanAmount : 0.0,
                                      Submitted = false
                                  };


            var finalResult = unAssignedLoans.Union(boardLoans).OrderBy(loan => loan.LoanNumber);
            if (board.BoardLoans.Count() > 0)
                return finalResult;
            return boardLoans;
        }

        /// <summary>
        /// Gets the loan.
        /// </summary>
        /// <param name="boardId">The board id.</param>
        /// <returns></returns>
        public IBoard GetLoan(Guid boardId)
        {
            return _loanRepository.GetBoardByID(boardId);
        }

        /// <summary>
        /// Gets the committee banlk account plus dues.
        /// </summary>
        /// <param name="projectId">The project id.</param>
        /// <param name="boardDate">The board date.</param>
        /// <returns></returns>
        public double GetCommitteeBanlkAccountPlusDues(Guid projectId, DateTime boardDate)
        {
            var commiteeBankAccount = _administrationRepository.FindCommitteeBankAccounts(
                bankAcc => bankAcc.CommitteeID == RegistryInfo.CommitteeID && bankAcc.ProjectID == projectId).First();
            double accountbalance = _financeService.GetBankAccountBalance(commiteeBankAccount);

            var paymentLoans = _loanRepository.FindCustomerLoans(
                loan => _wfService.GetInStep(Steps.PaymentList).Contains(loan.LoanID) && loan.ProjectStage.ProjectID == projectId);
            var executions = paymentLoans.Where(loan => loan.LoanExecutionLst.Count() > 0).Select(
                loan => loan.LoanExecutionLst.Where(ex => ex.LoanExecutionStatus).OrderBy(ex => ex.ExecutionDate).Last());

            double duesToBoardDate = executions.Select(exe => _loanExecutionService.GetAllExcuationDues(exe.LoanExecutionID)).Sum();

            return accountbalance + duesToBoardDate;
        }

        /// <summary>
        /// Gets the member objects.
        /// </summary>
        /// <param name="boardMembers">The board members.</param>
        /// <returns></returns>
        public IEnumerable GetMemberObjects(List<IBoardMember> boardMembers)
        {
            return _membershipRepository.FindMembers(user => boardMembers.Select(mem => mem.BoardMemberID).Contains(user.BoardMemberID)).ToList();
        }

        /// <summary>
        /// Gets the new board committee number.
        /// </summary>
        /// <returns></returns>
        public int GetNewBoardCommitteeNumber()
        {
            return _loanRepository.GetLastBoardNumber() + 1;
        }

        /// <summary>
        /// Valids the bord date.
        /// </summary>
        /// <param name="boardDate">The board date.</param>
        /// <param name="boardId">The board id.</param>
        /// <returns></returns>
        public bool ValidBordDate(DateTime boardDate, Guid boardId)
        {
            var isOldboardDate = false;
            IBoard oldBoard;
            if (!boardId.IsEmpty())
            {
                oldBoard = _loanRepository.GetBoardByID(boardId);
                isOldboardDate = oldBoard.BoardDate.Month.Equals(boardDate.Month) &&
                    oldBoard.BoardDate.Year.Equals(boardDate.Year);
            }

            var results = _loanRepository.FindBoard(board => board.BoardDate.Month.Equals(boardDate.Month) &&
                board.BoardDate.Year.Equals(boardDate.Year)
                ).Count();
            return results < 2 || isOldboardDate;
        }
    }
}
